Skip to content

Introduce studio preview components and friendly controls; refactor DomainPanel and Header - #401

Closed
jackgranatowski wants to merge 8 commits into
mainfrom
codex/przeprojektuj-configurator-dla-poprawy-uzytecznosci
Closed

Introduce studio preview components and friendly controls; refactor DomainPanel and Header#401
jackgranatowski wants to merge 8 commits into
mainfrom
codex/przeprojektuj-configurator-dla-poprawy-uzytecznosci

Conversation

@jackgranatowski

@jackgranatowski jackgranatowski commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Motivation

  • Surface richer, contextual previews and a friendlier control UI so designers can edit tokens with live specimens and clearer affordances.
  • Consolidate repeated disclosure/card UI into reusable components and infer control types to simplify token editors and previews.
  • Improve the top header for compactness and clearer session/workspace controls.

Description

  • Added new reusable components: CategoryHeader, ControlSection, ControlPreview, FriendlyControl, and StudioFrame, plus multiple studio views under components/editors (TypographyStudio, ColorStudio, SpacingStudio, LayoutStudio, ShapeStudio, ShadowStudio, MotionStudio, EffectsStudio) to present domain-specific visual canvases.
  • Introduced controlSchema.js with controlForToken() to heuristically map tokens to control types and preview kinds (color, font, length, motion, radius, shadow, etc.).
  • Reworked DomainPanel.svelte to use CategoryHeader, ControlSection, and FriendlyControl, and to embed the new studio views for matching domains; removed the previous manual header and expanded card markup in favor of the new components.
  • Reworked Header.svelte markup and styles for a simplified brand row, compact search/command area, grouped action sets, improved responsive behavior, and share/export button semantics.
  • Updated SmartSettings.svelte to render FriendlyControl instead of raw TokenRow in many places, and applied small accessibility/focus tweaks to HeadingEditor.svelte and RadiusEditor.svelte (added tabindex on tablists).

Testing

  • Ran the project's build using npm run build to validate bundling and Svelte compilation, which completed successfully.
  • Executed the automated test suite via npm test and the tests passed without regressions.

Codex Task

Summary by CodeRabbit

  • New Features
    • Added new studio UI building blocks, including reusable studio frames, workflow navigation, and friendly token controls.
    • Introduced refreshed Studio editors for Typography, Color, Spacing, Layout, Shape, Shadow, Motion, and Effects with richer previews and curated control sections.
    • Added smarter token-to-control mapping to generate consistent control schemas and previews.
  • Bug Fixes
    • Improved keyboard accessibility by making editor tablists focusable.
  • Tests
    • Added smoke and interaction tests for redesigned Studio editors, plus schema validation for studio definitions.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds shared control and studio components, rewires domain panels and smart settings to use friendly token cards, refreshes the header layout, and enables keyboard focus on two editor tablists.

Changes

Configurator control and studio UI

Layer / File(s) Summary
Shared control primitives
configurator/src/lib/controlSchema.js, configurator/src/components/ControlPreview.svelte, configurator/src/components/ControlSection.svelte, configurator/src/components/CategoryHeader.svelte
Adds token control schema classification, preview variants, collapsible control sections, and the category header component.
Studio schema and frame
configurator/src/lib/studioSchema.js, configurator/src/components/editors/StudioFrame.svelte, configurator/src/components/editors/StudioControls.svelte, configurator/tests/studio-schema.test.js
Adds typography panel data, studio group definitions, group resolution, the shared studio frame, and the shared studio controls renderer with schema validation tests.
Studio views
configurator/src/components/editors/*Studio.svelte, configurator/tests-components/studios.test.js
Adds the typography, color, spacing, layout, shape, shadow, motion, and effects studio views, plus component smoke and interaction tests.
Friendly controls
configurator/src/components/FriendlyControl.svelte, configurator/src/components/SmartSettings.svelte
Adds FriendlyControl cards and switches SmartSettings token rows to render them.
Domain panel integration
configurator/src/components/DomainPanel.svelte
Replaces the panel header with category content, mounts the studio components by domain, and rewrites curated controls to use ControlSection and FriendlyControl.
Header refresh
configurator/src/components/Header.svelte
Reworks the top bar layout, active-domain status, action groups, and responsive styling.
Tablist keyboard focus
configurator/src/components/HeadingEditor.svelte, configurator/src/components/RadiusEditor.svelte
Adds keyboard focus to the heading and radius tablist containers while keeping their existing handlers.

Sequence Diagram(s)

sequenceDiagram
  participant DomainPanel
  participant StudioControls
  participant FriendlyControl
  participant ControlPreview
  participant overrides

  DomainPanel->>StudioControls: render resolved studio groups
  StudioControls->>FriendlyControl: render each token control
  FriendlyControl->>overrides: read or update override
  FriendlyControl->>ControlPreview: render token preview
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • codeslash-dev/SLASHED#302: Also refactors DomainPanel.svelte and Header.svelte around domain-specific rendering and top-bar structure.
  • codeslash-dev/SLASHED#303: Also changes DomainPanel.svelte and Header.svelte to reorganize the configurator panel and controls.
  • codeslash-dev/SLASHED#398: Also updates DomainPanel, HeadingEditor, RadiusEditor, and SmartSettings for editor UI and fold/persistence behavior.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main additions and refactors in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/przeprojektuj-configurator-dla-poprawy-uzytecznosci

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Configurator tests

Failed stage: Run npm test [❌]

Failed test name: Share link button > is disabled until there is a customised token

Failure summary:

The action failed because 2 Vitest component tests in tests-components/share-button.test.js failed.

- Both failures are TestingLibraryElementError: Unable to find a label with the text of: Copy
shareable configuration link, meaning the rendered Header component does not include an element
accessible via that label (missing/changed aria-label/label text, or the button is not rendered).
-
The first failure occurs in Share link button > is disabled until there is a customised token at
tests-components/share-button.test.js:25:17 where getByLabelText('Copy shareable configuration
link') throws.
- The second failure occurs in Share link button > copies a URL whose fragment
restores the current config at tests-components/share-button.test.js:39:17 for the same reason.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

1091:  type: 'test'
1092:  ...
1093:  1..3
1094:  ok 28 - bestContrastVsBW
1095:  ---
1096:  duration_ms: 0.662595
1097:  type: 'suite'
1098:  ...
1099:  # Subtest: wcagLevel
1100:  # Subtest: classifies known thresholds
1101:  ok 1 - classifies known thresholds
1102:  ---
1103:  duration_ms: 0.174366
1104:  type: 'test'
1105:  ...
1106:  # Subtest: non-finite ratios fail
1107:  ok 2 - non-finite ratios fail
1108:  ---
...

3935:  ok 7 - storage key is versioned
3936:  ---
3937:  duration_ms: 0.32701
3938:  type: 'test'
3939:  ...
3940:  1..7
3941:  ok 87 - sanitiseUiState
3942:  ---
3943:  duration_ms: 5.844937
3944:  type: 'suite'
3945:  ...
3946:  1..87
3947:  # tests 522
3948:  # suites 87
3949:  # pass 522
3950:  # fail 0
3951:  # cancelled 0
3952:  # skipped 0
3953:  # todo 0
3954:  # duration_ms 1077.526109
3955:  > slashed-configurator@0.6.14 test:components
3956:  > vitest run
3957:  �[1m�[46m RUN �[49m�[22m �[36mv3.2.6 �[39m�[90m/home/runner/work/SLASHED/SLASHED/configurator�[39m
3958:  �[32m✓�[39m tests-components/icon.test.js �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 42�[2mms�[22m�[39m
3959:  �[32m✓�[39m tests-components/bundle-picker.test.js �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 299�[2mms�[22m�[39m
3960:  �[32m✓�[39m tests-components/domain-preview.test.js �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[33m 1147�[2mms�[22m�[39m
3961:  �[31m❯�[39m tests-components/share-button.test.js �[2m(�[22m�[2m2 tests�[22m�[2m | �[22m�[31m2 failed�[39m�[2m)�[22m�[32m 72�[2mms�[22m�[39m
3962:  �[31m   �[31m�[31m Share link button�[2m > �[22mis disabled until there is a customised token�[39m�[32m 61�[2mms�[22m�[39m
...

4306:  �[36m</button>�[31m
4307:  �[0m �[0m
4308:  �[36m<button�[31m
4309:  �[33mclass�[31m=�[32m"hdr__export svelte-oiwvqb hdr__export--ready"�[31m
4310:  �[33mtitle�[31m=�[32m"Open export drawer"�[31m
4311:  �[36m>�[31m
4312:  �[0mExport CSS�[0m
4313:  �[36m</button>�[31m
4314:  �[36m</div>�[31m
4315:  �[36m</nav>�[31m
4316:  �[36m</header>�[31m
4317:  �[0m�[0m
4318:  �[36m</div>�[31m
4319:  �[36m</body>�[31m�[39m
4320:  �[32m✓�[39m tests-components/shade-ramp.test.js �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[33m 434�[2mms�[22m�[39m
4321:  �[31m⎯⎯⎯⎯⎯⎯⎯�[39m�[1m�[41m Failed Tests 2 �[49m�[22m�[31m⎯⎯⎯⎯⎯⎯⎯�[39m
4322:  �[41m�[1m FAIL �[22m�[49m tests-components/share-button.test.js�[2m > �[22mShare link button�[2m > �[22mis disabled until there is a customised token
4323:  �[31m�[1mTestingLibraryElementError�[22m�[39m: Unable to find a label with the text of: Copy shareable configuration link
4324:  Ignored nodes: comments, script, style
...

4487:  �[0m◨�[0m
4488:  �[36m</button>�[39m
4489:  �[0m �[0m
4490:  �[36m<button�[39m
4491:  �[33mclass�[39m=�[32m"hdr__export svelte-oiwvqb"�[39m
4492:  �[33mtitle�[39m=�[32m"Open export drawer"�[39m
4493:  �[36m>�[39m
4494:  �[0mExport CSS�[0m
4495:  �[36m</button>�[39m
4496:  �[36m</div>�[39m
4497:  �[36m</nav>�[39m
4498:  �[36m</header>�[39m
4499:  �[0m�[0m
4500:  �[36m</div>�[39m
4501:  �[36m</body>�[39m
4502:  �[90m �[2m❯�[22m Object.getElementError node_modules/@testing-library/dom/dist/config.js:�[2m37:19�[22m�[39m
4503:  �[90m �[2m❯�[22m getAllByLabelText node_modules/@testing-library/dom/dist/queries/label-text.js:�[2m111:38�[22m�[39m
4504:  �[90m �[2m❯�[22m node_modules/@testing-library/dom/dist/query-helpers.js:�[2m52:17�[22m�[39m
4505:  �[90m �[2m❯�[22m node_modules/@testing-library/dom/dist/query-helpers.js:�[2m95:19�[22m�[39m
4506:  �[36m �[2m❯�[22m tests-components/share-button.test.js:�[2m25:17�[22m�[39m
4507:  �[90m 23| �[39m  �[34mtest�[39m(�[32m'is disabled until there is a customised token'�[39m�[33m,�[39m �[35masync�[39m () �[33m=>�[39m {
4508:  �[90m 24| �[39m    �[35mconst�[39m { getByLabelText } �[33m=�[39m �[34mrender�[39m(�[33mHeader�[39m)�[33m;�[39m
4509:  �[90m 25| �[39m    �[35mconst�[39m btn �[33m=�[39m �[34mgetByLabelText�[39m(�[32m'Copy shareable configuration link'�[39m)�[33m;�[39m
4510:  �[90m   | �[39m                �[31m^�[39m
4511:  �[90m 26| �[39m    �[34mexpect�[39m(btn)�[33m.�[39m�[34mtoBeDisabled�[39m()�[33m;�[39m
4512:  �[90m 27| �[39m
4513:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯�[22m�[39m
4514:  �[41m�[1m FAIL �[22m�[49m tests-components/share-button.test.js�[2m > �[22mShare link button�[2m > �[22mcopies a URL whose fragment restores the current config
4515:  �[31m�[1mTestingLibraryElementError�[22m�[39m: Unable to find a label with the text of: Copy shareable configuration link
4516:  Ignored nodes: comments, script, style
...

4677:  �[0m◨�[0m
4678:  �[36m</button>�[39m
4679:  �[0m �[0m
4680:  �[36m<button�[39m
4681:  �[33mclass�[39m=�[32m"hdr__export svelte-oiwvqb hdr__export--ready"�[39m
4682:  �[33mtitle�[39m=�[32m"Open export drawer"�[39m
4683:  �[36m>�[39m
4684:  �[0mExport CSS�[0m
4685:  �[36m</button>�[39m
4686:  �[36m</div>�[39m
4687:  �[36m</nav>�[39m
4688:  �[36m</header>�[39m
4689:  �[0m�[0m
4690:  �[36m</div>�[39m
4691:  �[36m</body>�[39m
4692:  �[90m �[2m❯�[22m Object.getElementError node_modules/@testing-library/dom/dist/config.js:�[2m37:19�[22m�[39m
4693:  �[90m �[2m❯�[22m getAllByLabelText node_modules/@testing-library/dom/dist/queries/label-text.js:�[2m111:38�[22m�[39m
4694:  �[90m �[2m❯�[22m node_modules/@testing-library/dom/dist/query-helpers.js:�[2m52:17�[22m�[39m
4695:  �[90m �[2m❯�[22m node_modules/@testing-library/dom/dist/query-helpers.js:�[2m95:19�[22m�[39m
4696:  �[36m �[2m❯�[22m tests-components/share-button.test.js:�[2m39:17�[22m�[39m
4697:  �[90m 37| �[39m    �[34msetOverride�[39m(realToken�[33m,�[39m �[32m'2rem'�[39m)�[33m;�[39m
4698:  �[90m 38| �[39m    �[35mconst�[39m { getByLabelText } �[33m=�[39m �[34mrender�[39m(�[33mHeader�[39m)�[33m;�[39m
4699:  �[90m 39| �[39m    �[35mconst�[39m btn �[33m=�[39m �[34mgetByLabelText�[39m(�[32m'Copy shareable configuration link'�[39m)�[33m;�[39m
4700:  �[90m   | �[39m                �[31m^�[39m
4701:  �[90m 40| �[39m
4702:  �[90m 41| �[39m    �[35mawait�[39m fireEvent�[33m.�[39m�[34mclick�[39m(btn)�[33m;�[39m
4703:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯�[22m�[39m
4704:  �[2m Test Files �[22m �[1m�[31m1 failed�[39m�[22m�[2m | �[22m�[1m�[32m4 passed�[39m�[22m�[90m (5)�[39m
4705:  �[2m      Tests �[22m �[1m�[31m2 failed�[39m�[22m�[2m | �[22m�[1m�[32m23 passed�[39m�[22m�[90m (25)�[39m
4706:  �[2m   Start at �[22m 21:13:12
4707:  �[2m   Duration �[22m 3.73s�[2m (transform 1.15s, setup 388ms, collect 3.72s, tests 1.99s, environment 2.79s, prepare 612ms)�[22m
4708:  ##[error]TestingLibraryElementError: Unable to find a label with the text of: Copy shareable configuration link
4709:  
...

4873:              
4874:            </button>
4875:             
4876:            <button
4877:              class="hdr__export svelte-oiwvqb"
4878:              title="Open export drawer"
4879:            >
4880:              Export CSS
4881:            </button>
4882:          </div>
4883:        </nav>
4884:      </header>
4885:      
4886:    </div>
4887:  </body>
4888:   ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
4889:   ❯ getAllByLabelText node_modules/@testing-library/dom/d
4890:  ##[error]TestingLibraryElementError: Unable to find a label with the text of: Copy shareable configuration link
4891:  
...

5053:              
5054:            </button>
5055:             
5056:            <button
5057:              class="hdr__export svelte-oiwvqb hdr__export--ready"
5058:              title="Open export drawer"
5059:            >
5060:              Export CSS
5061:            </button>
5062:          </div>
5063:        </nav>
5064:      </header>
5065:      
5066:    </div>
5067:  </body>
5068:   ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
5069:   ❯ getAllByLabelText node_modules/@testing-library/dom/dist/queries/label-text.js:111:
5070:  ##[error]Process completed with exit code 1.
5071:  Post job cleanup.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add studio previews and FriendlyControl UI; refactor DomainPanel and Header
✨ Enhancement 🐞 Bug fix 🕐 40+ Minutes

Grey Divider

Description

• Add reusable category header, disclosure sections, token previews, and friendly controls.
• Introduce domain “Studio” canvases to preview tokens in realistic component contexts.
• Refactor DomainPanel/Header to use the new UI patterns and improve accessibility.
Diagram

graph TD
  DP["DomainPanel"] --> CH["CategoryHeader"]
  DP --> CS["ControlSection"] --> FC["FriendlyControl"] --> CP["ControlPreview"] --> PREV["preview.js + store"]
  FC --> SCHEMA["controlSchema.js"]
  DP --> STUDIO["Domain Studios"] --> SF["StudioFrame"] --> PREV
  HDR["Header"] --> STATE["UI state + domains"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Data-driven control metadata (token annotations)
  • ➕ Eliminates heuristic drift as token naming evolves
  • ➕ Enables domain-specific labels/descriptions/options without regex rules
  • ➕ Easier to test: metadata is explicit and versionable
  • ➖ Requires enriching the token source/catalogue format and migration work
  • ➖ More upfront authoring effort for metadata across many tokens
2. Per-domain control schemas (maps by domain)
  • ➕ Keeps logic simple and predictable within each domain
  • ➕ Avoids global regex rules that can misclassify edge-case tokens
  • ➖ Schema duplication across domains for shared token patterns (length/color/etc.)
  • ➖ Harder to keep consistent if many domains share similar token families
3. Pluggable inference pipeline (explicit → domain rules → global heuristics)
  • ➕ Preserves convenience of heuristics while allowing targeted overrides
  • ➕ Scales as studios add bespoke control behaviors
  • ➖ More abstraction/indirection; may be overkill unless misclassification becomes common

Recommendation: Current heuristic-based controlSchema is a pragmatic starting point and keeps editors lightweight. If misclassifications start to appear (or token naming becomes less consistent), evolve toward a pluggable inference pipeline with a growing explicit/override layer (possibly per-domain) before committing to full catalogue-level metadata.

Files changed (19) +370 / -311

Enhancement (12) +221 / -0
CategoryHeader.svelteAdd reusable domain hero header with stats and reset action +33/-0

Add reusable domain hero header with stats and reset action

• Introduces a CategoryHeader component that renders the domain icon/label/blurb plus override and token counts. Conditionally shows a “Reset category” button when overrides exist and a reset callback is provided.

configurator/src/components/CategoryHeader.svelte

ControlPreview.svelteAdd visual token specimen previews tied to preview theme +49/-0

Add visual token specimen previews tied to preview theme

• Adds a compact preview renderer that chooses a specimen based on a preview type (color/font/radius/shadow/motion/etc.). Uses preview-theme declarations and active override/value to render contextual samples.

configurator/src/components/ControlPreview.svelte

FriendlyControl.svelteAdd FriendlyControl token row with inferred metadata, preview, and reset +44/-0

Add FriendlyControl token row with inferred metadata, preview, and reset

• Introduces a higher-level token row component that derives label/description/control + preview type via controlSchema and renders TokenEditor plus ControlPreview. Adds affordances for showing/hiding the raw token name and resetting an override.

configurator/src/components/FriendlyControl.svelte

ColorStudio.svelteAdd Color Studio palette + role-based usage previews +16/-0

Add Color Studio palette + role-based usage previews

• Adds a studio canvas showing curated brand color roles with a palette grid and a small UI usage mock (button/card/status). Pulls active values from overrides to reflect edits live.

configurator/src/components/editors/ColorStudio.svelte

EffectsStudio.svelteAdd Effects Studio canvas for scrim/blur/opacity context +3/-0

Add Effects Studio canvas for scrim/blur/opacity context

• Introduces a simple studio specimen demonstrating blur/scrim/opacity behavior on a “media card”-style surface.

configurator/src/components/editors/EffectsStudio.svelte

LayoutStudio.svelteAdd Layout Studio canvas for container/grid/gap visualization +3/-0

Add Layout Studio canvas for container/grid/gap visualization

• Adds a studio specimen that visualizes viewport vs container width and a small grid content area, making layout tokens easier to reason about.

configurator/src/components/editors/LayoutStudio.svelte

MotionStudio.svelteAdd Motion Studio animated duration specimens +3/-0

Add Motion Studio animated duration specimens

• Introduces an animated chip-based canvas that reflects duration tokens as motion rather than static numbers.

configurator/src/components/editors/MotionStudio.svelte

ShadowStudio.svelteAdd Shadow Studio elevation stack preview +3/-0

Add Shadow Studio elevation stack preview

• Adds a studio canvas that renders multiple shadow levels as raised cards to compare elevation strength in context.

configurator/src/components/editors/ShadowStudio.svelte

ShapeStudio.svelteAdd Shape Studio component silhouettes for radius/border/focus context +3/-0

Add Shape Studio component silhouettes for radius/border/focus context

• Introduces a studio specimen showing multiple component-like shapes to preview radius/border/focus-ring related tokens.

configurator/src/components/editors/ShapeStudio.svelte

SpacingStudio.svelteAdd Spacing Studio ruler and card rhythm preview +3/-0

Add Spacing Studio ruler and card rhythm preview

• Adds a studio canvas with a spacing “ruler” and sample cards using spacing-related CSS variables to provide immediate visual scale feedback.

configurator/src/components/editors/SpacingStudio.svelte

TypographyStudio.svelteAdd Typography Studio specimen canvas with font overrides +24/-0

Add Typography Studio specimen canvas with font overrides

• Adds a studio canvas rendering a large typographic specimen driven by heading/body/mono font overrides, plus simple tabs for future focus modes.

configurator/src/components/editors/TypographyStudio.svelte

controlSchema.jsAdd controlForToken() heuristics to infer control and preview types +37/-0

Add controlForToken() heuristics to infer control and preview types

• Introduces token-to-control inference based on token name/value patterns plus a small explicit override map for special cases. Returns control metadata (label/description/control/preview/options) used by FriendlyControl and previews.

configurator/src/lib/controlSchema.js

Bug fix (2) +2 / -2
HeadingEditor.svelteMake heading tablist focusable for keyboard navigation +1/-1

Make heading tablist focusable for keyboard navigation

• Adds tabindex=0 to the tablist container so the keyboard navigation handler can be reached reliably via focus.

configurator/src/components/HeadingEditor.svelte

RadiusEditor.svelteMake radius tablist focusable for keyboard navigation +1/-1

Make radius tablist focusable for keyboard navigation

• Adds tabindex=0 to the tablist container to improve focusability and keyboard accessibility.

configurator/src/components/RadiusEditor.svelte

Refactor (5) +147 / -309
ControlSection.svelteIntroduce collapsible control section wrapper with modified badge +26/-0

Introduce collapsible control section wrapper with modified badge

• Adds a reusable details/summary wrapper for grouping controls with a hint string and modified-count badge. Replaces duplicated disclosure/card markup previously embedded in panels.

configurator/src/components/ControlSection.svelte

DomainPanel.svelteRefactor domain panel to new header/sections and embed studios +37/-74

Refactor domain panel to new header/sections and embed studios

• Replaces the custom panel header with CategoryHeader and swaps grouped TokenRow rendering for ControlSection + FriendlyControl. Adds conditional embedding of domain-specific Studio views to provide rich contextual previews per domain.

configurator/src/components/DomainPanel.svelte

Header.svelteRedesign header layout with status strip and grouped action pills +57/-233

Redesign header layout with status strip and grouped action pills

• Refactors the top bar into a compact brand row, a combined command/search area with domain + override + variable status, and grouped action clusters (history/session/workspace). Improves responsive behavior and simplifies export/share semantics while wiring domain label via DOMAIN_BY_ID.

configurator/src/components/Header.svelte

SmartSettings.svelteSwitch smart settings rows to FriendlyControl rendering +3/-2

Switch smart settings rows to FriendlyControl rendering

• Replaces TokenRow usage for smart section controls with FriendlyControl to standardize the friendlier copy + preview + reset affordances across the app.

configurator/src/components/SmartSettings.svelte

StudioFrame.svelteAdd shared StudioFrame wrapper applying preview theme to canvases +24/-0

Add shared StudioFrame wrapper applying preview theme to canvases

• Introduces a reusable frame component that applies preview declarations based on overrides + selected preview theme. Standardizes studio title/eyebrow/description layout and responsive behavior.

configurator/src/components/editors/StudioFrame.svelte

@qodo-code-review

qodo-code-review Bot commented Jun 24, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 5 rules

Grey Divider


Remediation recommended

1. Reduced-motion ignored in previews 🐞 Bug ≡ Correctness
Description
The new mini preview stages (ControlPreview and StudioFrame) ignore ui.previewMotion, so they
do not apply the --sf-motion-scale: 0 override used elsewhere and animations keep running in
reduced-motion mode. This makes reduced-motion behavior inconsistent across the app.
Code

configurator/src/components/ControlPreview.svelte[6]

+  const stageStyle = $derived(buildPreviewDeclarations(overrides, ui.previewTheme));
Relevance

⭐⭐⭐ High

Reduced-motion inconsistencies in previews were fixed/accepted before (DomainPreview motion bars) in
PR #371.

PR-#371

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The preview pane and DomainPreview explicitly append --sf-motion-scale: 0 in reduced-motion mode,
but ControlPreview/StudioFrame do not, so their stages will continue to animate. This matches a
previously accepted bug pattern around reduced-motion inconsistencies in preview animations.

configurator/src/components/Preview.svelte[87-92]
configurator/src/components/DomainPreview.svelte[30-34]
configurator/src/components/ControlPreview.svelte[1-7]
configurator/src/components/editors/StudioFrame.svelte[1-9]
PR-#371

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Reduced-motion mode is implemented elsewhere by appending `--sf-motion-scale: 0;` to the stage declarations when `ui.previewMotion === 'reduced'`. The newly added preview stages (ControlPreview + StudioFrame) build stage declarations from only `overrides` + `ui.previewTheme`, so reduced-motion does not affect them.

### Issue Context
Preview.svelte and DomainPreview.svelte already implement the intended reduced-motion behavior by modifying the stage style string.

### Fix Focus Areas
- configurator/src/components/ControlPreview.svelte[1-10]
- configurator/src/components/editors/StudioFrame.svelte[1-9]
- configurator/src/components/Preview.svelte[87-92]
- configurator/src/components/DomainPreview.svelte[30-34]

### Implementation guidance
- Mirror the existing pattern:
 - Build `baseStyle = buildPreviewDeclarations(overrides, ui.previewTheme)`
 - If `ui.previewMotion === 'reduced'`, append `\n--sf-motion-scale: 0;`
- If you implement the performance fix by introducing a shared wrapper stage, apply this reduced-motion logic at the wrapper (so both performance + reduced-motion are addressed together).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Per-row full preview cascade 🐞 Bug ➹ Performance
Description
ControlPreview generates a full buildPreviewDeclarations() string (defaults for every token +
overrides) and inlines it into each preview instance, multiplying O(allTokens) work and large style
attributes across long lists (DomainPanel/SmartSettings). This is likely to cause noticeable UI
slowness and memory bloat when many FriendlyControl rows render at once.
Code

configurator/src/components/ControlPreview.svelte[R5-10]

+  const active = $derived(value || overrides[token?.name] || token?.value || '');
+  const stageStyle = $derived(buildPreviewDeclarations(overrides, ui.previewTheme));
+</script>
+
+<div class="ctrl-preview" style={stageStyle} title={token?.name || label}>
+  {#if type === 'color'}
Relevance

⭐⭐⭐ High

Team accepted removing per-row expensive preview/probe recomputation (BrandColorRow) for perf in PR
#398.

PR-#398

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
ControlPreview calls buildPreviewDeclarations and assigns the resulting full declaration string to
the style attribute of every preview node. buildPreviewDeclarations loops through all catalog
tokens plus all overrides, so this work and resulting string get duplicated for each
FriendlyControl row rendered in DomainPanel/SmartSettings.

configurator/src/components/ControlPreview.svelte[1-12]
configurator/src/lib/preview.js[28-46]
configurator/src/components/FriendlyControl.svelte[14-25]
configurator/src/components/DomainPanel.svelte[354-373]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ControlPreview` computes `stageStyle` by calling `buildPreviewDeclarations(overrides, ui.previewTheme)` and applies it directly to each preview node. `buildPreviewDeclarations` iterates `allTokens` and then `Object.entries(overrides)`, producing a large declaration string; doing this per row scales poorly.

### Issue Context
`FriendlyControl` renders `ControlPreview` for every token row in DomainPanel and SmartSettings, so the cost multiplies by the number of visible rows.

### Fix Focus Areas
- configurator/src/components/ControlPreview.svelte[1-12]
- configurator/src/lib/preview.js[28-46]
- configurator/src/components/FriendlyControl.svelte[14-28]
- configurator/src/components/DomainPanel.svelte[354-373]

### Implementation guidance
1) Move the `buildPreviewDeclarations(...)` call to a higher-level “stage” wrapper (e.g., DomainPanel section / SmartSettings section), apply it once via `style={stageStyle}` on that wrapper, and let child previews inherit the resolved `--sf-*` variables.
2) Update `ControlPreview` to *not* call `buildPreviewDeclarations` or set the full cascade; it should only render the small specimen and set minimal per-preview inline styles (e.g., `--sample`).
3) If a wrapper approach is not feasible everywhere, at minimum compute the cascade once and pass it down as a prop (still not ideal because it duplicates large style attributes, but it avoids repeated O(allTokens) computation).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. motion-scale preview invalid 🐞 Bug ≡ Correctness
Description
controlSchema maps --sf-motion-scale to preview type motion, but that token’s active value is
unitless (e.g. 1), and ControlPreview assigns it to animation-duration, which requires a time
unit and becomes invalid CSS. This produces broken/misleading motion previews for the motion-scale
control.
Code

configurator/src/lib/controlSchema.js[R22-24]

+  } else if (/duration|motion-scale/.test(name)) {
+    schema = { ...schema, control: 'length', preview: 'motion' };
+  } else if (/ease/.test(name)) {
Relevance

⭐⭐ Medium

No clear historical evidence for fixing unitless value causing invalid animation-duration in
previews.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The token default is unitless ("1"). controlSchema routes it to preview: 'motion', and
ControlPreview’s motion branch sets animation-duration to that unitless value, which is invalid
for CSS animation-duration and will not preview correctly.

configurator/src/lib/controlSchema.js[12-34]
configurator/src/components/ControlPreview.svelte[18-22]
configurator/src/data/api-index.generated.json[11341-11350]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`controlForToken()` classifies names matching `/duration|motion-scale/` as `{ preview: 'motion' }`. For `--sf-motion-scale` this is incorrect because its value is unitless (e.g. `1`), and `ControlPreview` uses `preview: 'motion'` to set `style:animation-duration={active}`.

### Issue Context
The catalogue default for `--sf-motion-scale` is the unitless string `"1"`.

### Fix Focus Areas
- configurator/src/lib/controlSchema.js[18-34]
- configurator/src/components/ControlPreview.svelte[18-23]
- configurator/src/data/api-index.generated.json[11341-11350]

### Implementation guidance
Option A (smallest change):
- Change the regex branch to only match `duration`, and add a separate special-case for `--sf-motion-scale` that uses a different preview kind (e.g. `motion-scale`).
- Add a corresponding `ControlPreview` branch that converts scale -> duration, e.g. `animation-duration: calc(var(--sf-duration-normal, 300ms) * <scale>)`.

Option B (simpler preview):
- Map `--sf-motion-scale` to `preview: 'scale'` and add a lightweight scale specimen (or let it fall back to the generic preview) so it no longer sets `animation-duration` to a unitless number.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
configurator/src/components/Header.svelte (1)

13-20: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clear pending share timer on unmount.

Line 19 schedules a timeout, but it is never cleaned up on teardown. If the header unmounts before it fires, the callback can run against disposed component state.

Suggested fix
 <script>
+  import { onDestroy } from 'svelte';
   import { sync, allTokens, frameworkVersion } from '../lib/model.js';
   import { DOMAIN_BY_ID } from '../lib/domains.js';
   import { ui, overrides, history, undo, redo, openOutputDrawer, currentShareUrl } from '../lib/store.svelte.js';
   import { copyText, COPY_FEEDBACK_MS } from '../lib/clipboard.js';
@@
   let shareCopied = $state(false);
   let _shareTimer;
+
+  onDestroy(() => {
+    clearTimeout(_shareTimer);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/components/Header.svelte` around lines 13 - 20, The share
timer in Header.svelte is not cleaned up when the component is destroyed, so add
teardown logic to clear the pending timeout used by shareLink and prevent it
from firing after unmount. Use the existing _shareTimer and shareLink symbols as
the place to manage the timer, and register a component cleanup/unmount handler
that clears any active timer and resets the reference.
🧹 Nitpick comments (1)
configurator/src/components/ControlPreview.svelte (1)

6-6: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Each preview rebuilds the full token declaration set — O(previews × allTokens) on every override change.

buildPreviewDeclarations iterates allTokens plus all overrides and serializes them (per configurator/src/lib/preview.js:23-47). Since DomainPanel/SmartSettings mount one ControlPreview per FriendlyControl, every override edit recomputes and re-serializes the entire token set once per visible card. For panels with many tokens this is a noticeable hot path. Consider lifting stageStyle to a single shared $derived (module-level memo keyed on overrides+ui.previewTheme) and passing it down, rather than recomputing inside each preview.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/components/ControlPreview.svelte` at line 6, The preview
styling work in ControlPreview.svelte is being recomputed independently for
every FriendlyControl, causing repeated full token serialization on each
override change. Move the stageStyle derivation out of the per-card
ControlPreview path and into a shared memoized or module-level $derived keyed by
overrides and ui.previewTheme, then pass the computed result down so
buildPreviewDeclarations is only run once per state change rather than once per
preview.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@configurator/src/components/editors/EffectsStudio.svelte`:
- Line 2: The StudioFrame description in EffectsStudio.svelte mixes English with
untranslated Polish text, so update the description copy to fully match the
English UI used by the rest of the studios. Locate the EffectsStudio component
and replace the current description string with a complete English sentence that
describes the blur, opacity, scrim, and compositing preview without any Polish
phrasing.

In `@configurator/src/components/editors/TypographyStudio.svelte`:
- Around line 11-20: The tab state in TypographyStudio.svelte is only used for
button styling and does not affect the specimen content, so the control is
misleading. Update the active tab logic in the tabs block and the specimen
rendering so selecting a tab actually switches what typography preview is shown,
using the existing active state and the tab-related symbols such as tabs,
active, and the specimen section.

In `@configurator/src/lib/controlSchema.js`:
- Around line 22-33: The preview kind mapping is incomplete: controlSchema now
emits gradient for --sf-scrim-direction and scale for numeric tokens, but
ControlPreview.svelte does not handle either and they fall back to spacing.
Update ControlPreview.svelte to add explicit branches for gradient and scale, or
remap those preview kinds to an existing renderer, ensuring the preview
selection matches the values produced by controlSchema.

---

Outside diff comments:
In `@configurator/src/components/Header.svelte`:
- Around line 13-20: The share timer in Header.svelte is not cleaned up when the
component is destroyed, so add teardown logic to clear the pending timeout used
by shareLink and prevent it from firing after unmount. Use the existing
_shareTimer and shareLink symbols as the place to manage the timer, and register
a component cleanup/unmount handler that clears any active timer and resets the
reference.

---

Nitpick comments:
In `@configurator/src/components/ControlPreview.svelte`:
- Line 6: The preview styling work in ControlPreview.svelte is being recomputed
independently for every FriendlyControl, causing repeated full token
serialization on each override change. Move the stageStyle derivation out of the
per-card ControlPreview path and into a shared memoized or module-level $derived
keyed by overrides and ui.previewTheme, then pass the computed result down so
buildPreviewDeclarations is only run once per state change rather than once per
preview.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 36ba43a4-a48e-4a61-ac59-394b962e9c13

📥 Commits

Reviewing files that changed from the base of the PR and between 7cef166 and 6e0894a.

📒 Files selected for processing (19)
  • configurator/src/components/CategoryHeader.svelte
  • configurator/src/components/ControlPreview.svelte
  • configurator/src/components/ControlSection.svelte
  • configurator/src/components/DomainPanel.svelte
  • configurator/src/components/FriendlyControl.svelte
  • configurator/src/components/Header.svelte
  • configurator/src/components/HeadingEditor.svelte
  • configurator/src/components/RadiusEditor.svelte
  • configurator/src/components/SmartSettings.svelte
  • configurator/src/components/editors/ColorStudio.svelte
  • configurator/src/components/editors/EffectsStudio.svelte
  • configurator/src/components/editors/LayoutStudio.svelte
  • configurator/src/components/editors/MotionStudio.svelte
  • configurator/src/components/editors/ShadowStudio.svelte
  • configurator/src/components/editors/ShapeStudio.svelte
  • configurator/src/components/editors/SpacingStudio.svelte
  • configurator/src/components/editors/StudioFrame.svelte
  • configurator/src/components/editors/TypographyStudio.svelte
  • configurator/src/lib/controlSchema.js

@@ -0,0 +1,3 @@
<script>import StudioFrame from './StudioFrame.svelte';</script>
<StudioFrame title="Effects Studio" description="Blur, opacity, scrim i compositing pokazane jako gotowa karta."><div class="fx"><div><h4>Readable media card</h4><p>Scrim, blur and muted states preview.</p></div></div></StudioFrame>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

User-facing description contains untranslated Polish text.

The description reads "Blur, opacity, scrim i compositing pokazane jako gotowa karta." — the trailing clause is Polish and inconsistent with the English UI copy elsewhere in the studios.

✏️ Proposed copy fix
-<StudioFrame title="Effects Studio" description="Blur, opacity, scrim i compositing pokazane jako gotowa karta."><div class="fx"><div><h4>Readable media card</h4><p>Scrim, blur and muted states preview.</p></div></div></StudioFrame>
+<StudioFrame title="Effects Studio" description="Blur, opacity, scrim and compositing shown as a ready-made card."><div class="fx"><div><h4>Readable media card</h4><p>Scrim, blur and muted states preview.</p></div></div></StudioFrame>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<StudioFrame title="Effects Studio" description="Blur, opacity, scrim i compositing pokazane jako gotowa karta."><div class="fx"><div><h4>Readable media card</h4><p>Scrim, blur and muted states preview.</p></div></div></StudioFrame>
<StudioFrame title="Effects Studio" description="Blur, opacity, scrim and compositing shown as a ready-made card."><div class="fx"><div><h4>Readable media card</h4><p>Scrim, blur and muted states preview.</p></div></div></StudioFrame>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/components/editors/EffectsStudio.svelte` at line 2, The
StudioFrame description in EffectsStudio.svelte mixes English with untranslated
Polish text, so update the description copy to fully match the English UI used
by the rest of the studios. Locate the EffectsStudio component and replace the
current description string with a complete English sentence that describes the
blur, opacity, scrim, and compositing preview without any Polish phrasing.

Comment on lines +11 to +20
<div class="tabs">{#each tabs as t}<button class:active={active===t} onclick={() => active=t}>{t}</button>{/each}</div>
<div class="specimen">
<h1 style:font-family={headingFont}>The quick brown fox</h1>
<h2 style:font-family={headingFont}>Jumps over the lazy dog</h2>
<h3 style:font-family={headingFont}>Typography at every scale</h3>
<h4 style:font-family={headingFont}>Fluid, readable, precise</h4>
<strong style:font-family={headingFont}>Fine-tune each heading level</strong>
<code style:font-family={monoFont}>Pixel-perfect control</code>
<p style:font-family={bodyFont}>Body: The quick brown fox jumps over the lazy dog. A short paragraph shows body rhythm, line height and spacing at a glance.</p>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Tabs are stateful but functionally inert

active only changes button styling; the specimen never changes with tab selection. This makes the tab control misleading.

Suggested fix
-  <div class="specimen">
-    <h1 style:font-family={headingFont}>The quick brown fox</h1>
-    <h2 style:font-family={headingFont}>Jumps over the lazy dog</h2>
-    <h3 style:font-family={headingFont}>Typography at every scale</h3>
-    <h4 style:font-family={headingFont}>Fluid, readable, precise</h4>
-    <strong style:font-family={headingFont}>Fine-tune each heading level</strong>
-    <code style:font-family={monoFont}>Pixel-perfect control</code>
-    <p style:font-family={bodyFont}>Body: The quick brown fox jumps over the lazy dog. A short paragraph shows body rhythm, line height and spacing at a glance.</p>
-  </div>
+  <div class="specimen">
+    {`#if` active === 'All' || active === 'H1'}<h1 style:font-family={headingFont}>The quick brown fox</h1>{/if}
+    {`#if` active === 'All' || active === 'H2'}<h2 style:font-family={headingFont}>Jumps over the lazy dog</h2>{/if}
+    {`#if` active === 'All' || active === 'H3'}<h3 style:font-family={headingFont}>Typography at every scale</h3>{/if}
+    {`#if` active === 'All' || active === 'H4'}<h4 style:font-family={headingFont}>Fluid, readable, precise</h4>{/if}
+    {`#if` active === 'All' || active === 'H5' || active === 'H6'}<strong style:font-family={headingFont}>Fine-tune each heading level</strong>{/if}
+    {`#if` active === 'All' || active === 'Mono'}<code style:font-family={monoFont}>Pixel-perfect control</code>{/if}
+    {`#if` active === 'All' || active === 'Body'}<p style:font-family={bodyFont}>Body: The quick brown fox jumps over the lazy dog. A short paragraph shows body rhythm, line height and spacing at a glance.</p>{/if}
+  </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="tabs">{#each tabs as t}<button class:active={active===t} onclick={() => active=t}>{t}</button>{/each}</div>
<div class="specimen">
<h1 style:font-family={headingFont}>The quick brown fox</h1>
<h2 style:font-family={headingFont}>Jumps over the lazy dog</h2>
<h3 style:font-family={headingFont}>Typography at every scale</h3>
<h4 style:font-family={headingFont}>Fluid, readable, precise</h4>
<strong style:font-family={headingFont}>Fine-tune each heading level</strong>
<code style:font-family={monoFont}>Pixel-perfect control</code>
<p style:font-family={bodyFont}>Body: The quick brown fox jumps over the lazy dog. A short paragraph shows body rhythm, line height and spacing at a glance.</p>
</div>
<div class="tabs">{`#each` tabs as t}<button class:active={active===t} onclick={() => active=t}>{t}</button>{/each}</div>
<div class="specimen">
{`#if` active === 'All' || active === 'H1'}<h1 style:font-family={headingFont}>The quick brown fox</h1>{/if}
{`#if` active === 'All' || active === 'H2'}<h2 style:font-family={headingFont}>Jumps over the lazy dog</h2>{/if}
{`#if` active === 'All' || active === 'H3'}<h3 style:font-family={headingFont}>Typography at every scale</h3>{/if}
{`#if` active === 'All' || active === 'H4'}<h4 style:font-family={headingFont}>Fluid, readable, precise</h4>{/if}
{`#if` active === 'All' || active === 'H5' || active === 'H6'}<strong style:font-family={headingFont}>Fine-tune each heading level</strong>{/if}
{`#if` active === 'All' || active === 'Mono'}<code style:font-family={monoFont}>Pixel-perfect control</code>{/if}
{`#if` active === 'All' || active === 'Body'}<p style:font-family={bodyFont}>Body: The quick brown fox jumps over the lazy dog. A short paragraph shows body rhythm, line height and spacing at a glance.</p>{/if}
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/components/editors/TypographyStudio.svelte` around lines 11
- 20, The tab state in TypographyStudio.svelte is only used for button styling
and does not affect the specimen content, so the control is misleading. Update
the active tab logic in the tabs block and the specimen rendering so selecting a
tab actually switches what typography preview is shown, using the existing
active state and the tab-related symbols such as tabs, active, and the specimen
section.

Comment thread configurator/src/lib/controlSchema.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@configurator/src/components/editors/ColorStudio.svelte`:
- Line 16: The StudioFrame copy in ColorStudio is mixing English and Polish,
which breaks UI consistency. Update the description used with the Color Studio
title so it matches the rest of the view’s English strings, and verify the
surrounding localized labels in ColorStudio.svelte remain consistent with the
same language and tone.

In `@configurator/src/lib/controlSchema.js`:
- Around line 31-32: The gradient inference branch in controlSchema.js can set
control to select for direction-related names without providing options, so
update the schema logic in the controlSchema inference path to always attach a
valid options list when choosing select for gradient direction tokens. Use the
existing controlSchema handling around the scrim/gradient and direction checks
to derive or supply the direction values, so FriendlyControl.svelte can render
the select instead of falling back to the raw editor.
- Around line 35-48: The schema classification in the controlSchema logic is
matching broad font-related names before the more specific weight case, so
weight tokens are getting the wrong control type. Update the conditional order
in the controlSchema classifier so the weight-specific check is evaluated before
the generic /font/ branch, and ensure the existing number control path for
weight still applies after the reordering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a0656255-a25e-4adc-8f1b-bf1c2bdec125

📥 Commits

Reviewing files that changed from the base of the PR and between 6e0894a and 04072eb.

📒 Files selected for processing (16)
  • configurator/src/components/ControlPreview.svelte
  • configurator/src/components/DomainPanel.svelte
  • configurator/src/components/FriendlyControl.svelte
  • configurator/src/components/Header.svelte
  • configurator/src/components/editors/ColorStudio.svelte
  • configurator/src/components/editors/EffectsStudio.svelte
  • configurator/src/components/editors/LayoutStudio.svelte
  • configurator/src/components/editors/MotionStudio.svelte
  • configurator/src/components/editors/ShadowStudio.svelte
  • configurator/src/components/editors/ShapeStudio.svelte
  • configurator/src/components/editors/SpacingStudio.svelte
  • configurator/src/components/editors/StudioControls.svelte
  • configurator/src/components/editors/StudioFrame.svelte
  • configurator/src/components/editors/TypographyStudio.svelte
  • configurator/src/lib/controlSchema.js
  • configurator/src/lib/studioSchema.js
🚧 Files skipped from review as they are similar to previous changes (10)
  • configurator/src/components/editors/EffectsStudio.svelte
  • configurator/src/components/ControlPreview.svelte
  • configurator/src/components/editors/ShadowStudio.svelte
  • configurator/src/components/editors/MotionStudio.svelte
  • configurator/src/components/editors/StudioFrame.svelte
  • configurator/src/components/editors/TypographyStudio.svelte
  • configurator/src/components/FriendlyControl.svelte
  • configurator/src/components/editors/SpacingStudio.svelte
  • configurator/src/components/Header.svelte
  • configurator/src/components/DomainPanel.svelte

.map((name) => tokenByName.get(name)).filter(Boolean);
</script>

<StudioFrame title="Color Studio" description="Najpierw ustaw źródła marki, potem sprawdź semantic roles, statusy, shade ramp, kontrast i realne użycie koloru." tone="color">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mixed-language description. The title is English ("Color Studio") while the description is Polish, inconsistent with the otherwise English UI strings in this view (button labels, legends, etc.). Likely leftover/placeholder copy.

[localization_and_formatting]

Suggested fix
-<StudioFrame title="Color Studio" description="Najpierw ustaw źródła marki, potem sprawdź semantic roles, statusy, shade ramp, kontrast i realne użycie koloru." tone="color">
+<StudioFrame title="Color Studio" description="Set the brand sources first, then check semantic roles, statuses, shade ramp, contrast and real color usage." tone="color">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<StudioFrame title="Color Studio" description="Najpierw ustaw źródła marki, potem sprawdź semantic roles, statusy, shade ramp, kontrast i realne użycie koloru." tone="color">
<StudioFrame title="Color Studio" description="Set the brand sources first, then check semantic roles, statuses, shade ramp, contrast and real color usage." tone="color">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/components/editors/ColorStudio.svelte` at line 16, The
StudioFrame copy in ColorStudio is mixing English and Polish, which breaks UI
consistency. Update the description used with the Color Studio title so it
matches the rest of the view’s English strings, and verify the surrounding
localized labels in ColorStudio.svelte remain consistent with the same language
and tone.

Comment on lines +31 to +32
} else if (/scrim|gradient/.test(name)) {
schema = { ...schema, control: /direction/.test(name) ? 'select' : 'text', preview: 'gradient' };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add options when inferring gradient direction selects.

This branch can emit control: 'select' without options; FriendlyControl.svelte only renders selects when options exist, so generic gradient direction tokens fall back to the raw editor.

Proposed fix
   } else if (/scrim|gradient/.test(name)) {
-    schema = { ...schema, control: /direction/.test(name) ? 'select' : 'text', preview: 'gradient' };
+    const isDirection = /direction/.test(name);
+    schema = {
+      ...schema,
+      control: isDirection ? 'select' : 'text',
+      preview: 'gradient',
+      ...(isDirection ? { options: ['to top', 'to bottom', 'to right', 'to left', '135deg'] } : {})
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (/scrim|gradient/.test(name)) {
schema = { ...schema, control: /direction/.test(name) ? 'select' : 'text', preview: 'gradient' };
} else if (/scrim|gradient/.test(name)) {
const isDirection = /direction/.test(name);
schema = {
...schema,
control: isDirection ? 'select' : 'text',
preview: 'gradient',
...(isDirection ? { options: ['to top', 'to bottom', 'to right', 'to left', '135deg'] } : {})
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/lib/controlSchema.js` around lines 31 - 32, The gradient
inference branch in controlSchema.js can set control to select for
direction-related names without providing options, so update the schema logic in
the controlSchema inference path to always attach a valid options list when
choosing select for gradient direction tokens. Use the existing controlSchema
handling around the scrim/gradient and direction checks to derive or supply the
direction values, so FriendlyControl.svelte can render the select instead of
falling back to the raw editor.

Comment on lines +35 to +48
} else if (/font/.test(name)) {
schema = { ...schema, control: 'font', preview: 'font' };
} else if (/duration|motion-scale|animation|transition/.test(name)) {
schema = { ...schema, control: parseLength(value) ? 'length' : 'text', preview: 'motion' };
} else if (/radius/.test(name)) {
schema = { ...schema, control: parseLength(value) ? 'length' : 'number', preview: 'radius' };
} else if (/shadow/.test(name)) {
schema = { ...schema, control: 'text', preview: 'shadow' };
} else if (/opacity/.test(name)) {
schema = { ...schema, control: 'number', preview: 'opacity' };
} else if (/space|gap|gutter|pad|width|height|size|container|leading|tracking|border-width|divider|offset|inset/.test(name) || parseLength(value)) {
schema = { ...schema, control: 'length', preview: /leading/.test(name) ? 'line-height' : 'spacing' };
} else if (/scale|threshold|bias|weight|z-/.test(name) || /^-?\d*\.?\d+$/.test(value)) {
schema = { ...schema, control: 'number', preview: /weight/.test(name) ? 'font' : 'scale' };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move weight handling before the broad font match.

Line 35 catches any font-weight-style token before Line 47 can assign the intended numeric weight control.

Proposed fix
-  } else if (/font/.test(name)) {
-    schema = { ...schema, control: 'font', preview: 'font' };
+  } else if (/weight/.test(name)) {
+    schema = { ...schema, control: 'number', preview: 'font' };
+  } else if (/font/.test(name)) {
+    schema = { ...schema, control: 'font', preview: 'font' };
...
-  } else if (/scale|threshold|bias|weight|z-/.test(name) || /^-?\d*\.?\d+$/.test(value)) {
+  } else if (/scale|threshold|bias|z-/.test(name) || /^-?\d*\.?\d+$/.test(value)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (/font/.test(name)) {
schema = { ...schema, control: 'font', preview: 'font' };
} else if (/duration|motion-scale|animation|transition/.test(name)) {
schema = { ...schema, control: parseLength(value) ? 'length' : 'text', preview: 'motion' };
} else if (/radius/.test(name)) {
schema = { ...schema, control: parseLength(value) ? 'length' : 'number', preview: 'radius' };
} else if (/shadow/.test(name)) {
schema = { ...schema, control: 'text', preview: 'shadow' };
} else if (/opacity/.test(name)) {
schema = { ...schema, control: 'number', preview: 'opacity' };
} else if (/space|gap|gutter|pad|width|height|size|container|leading|tracking|border-width|divider|offset|inset/.test(name) || parseLength(value)) {
schema = { ...schema, control: 'length', preview: /leading/.test(name) ? 'line-height' : 'spacing' };
} else if (/scale|threshold|bias|weight|z-/.test(name) || /^-?\d*\.?\d+$/.test(value)) {
schema = { ...schema, control: 'number', preview: /weight/.test(name) ? 'font' : 'scale' };
} else if (/weight/.test(name)) {
schema = { ...schema, control: 'number', preview: 'font' };
} else if (/font/.test(name)) {
schema = { ...schema, control: 'font', preview: 'font' };
} else if (/duration|motion-scale|animation|transition/.test(name)) {
schema = { ...schema, control: parseLength(value) ? 'length' : 'text', preview: 'motion' };
} else if (/radius/.test(name)) {
schema = { ...schema, control: parseLength(value) ? 'length' : 'number', preview: 'radius' };
} else if (/shadow/.test(name)) {
schema = { ...schema, control: 'text', preview: 'shadow' };
} else if (/opacity/.test(name)) {
schema = { ...schema, control: 'number', preview: 'opacity' };
} else if (/space|gap|gutter|pad|width|height|size|container|leading|tracking|border-width|divider|offset|inset/.test(name) || parseLength(value)) {
schema = { ...schema, control: 'length', preview: /leading/.test(name) ? 'line-height' : 'spacing' };
} else if (/scale|threshold|bias|z-/.test(name) || /^-?\d*\.?\d+$/.test(value)) {
schema = { ...schema, control: 'number', preview: 'scale' };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/lib/controlSchema.js` around lines 35 - 48, The schema
classification in the controlSchema logic is matching broad font-related names
before the more specific weight case, so weight tokens are getting the wrong
control type. Update the conditional order in the controlSchema classifier so
the weight-specific check is evaluated before the generic /font/ branch, and
ensure the existing number control path for weight still applies after the
reordering.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@configurator/src/components/editors/SpacingStudio.svelte`:
- Line 11: The SpacingStudio description currently mixes Polish and English in
the user-facing text, which makes the UI inconsistent. Update the description on
the StudioFrame in SpacingStudio.svelte so it uses one locale consistently,
matching the rest of the English UI, and keep the wording aligned with the
“Spacing Studio” title and its layout/rhythm preview purpose.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7b1e0f3a-e123-4297-949a-3f51a20a6041

📥 Commits

Reviewing files that changed from the base of the PR and between 43a8365 and 7253eb7.

📒 Files selected for processing (3)
  • configurator/src/components/editors/ColorStudio.svelte
  • configurator/src/components/editors/SpacingStudio.svelte
  • configurator/tests-components/studios.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • configurator/tests-components/studios.test.js
  • configurator/src/components/editors/ColorStudio.svelte

const groups = resolveStudioGroups(STUDIO_GROUPS.spacing);
</script>

<StudioFrame title="Spacing Studio" description="Ruler, rhythm i layout preview zamiast samych wartości liczbowych: od globalnej skali po realne odstępy w komponentach i sekcjach.">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use consistent locale in the studio description text.

Line 11 mixes Polish and English in a user-facing description, which creates an inconsistent UX in an otherwise English UI.

Suggested edit
-<StudioFrame title="Spacing Studio" description="Ruler, rhythm i layout preview zamiast samych wartości liczbowych: od globalnej skali po realne odstępy w komponentach i sekcjach.">
+<StudioFrame title="Spacing Studio" description="Ruler, rhythm, and layout previews instead of raw numeric values—from global scale to real spacing in components and sections.">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<StudioFrame title="Spacing Studio" description="Ruler, rhythm i layout preview zamiast samych wartości liczbowych: od globalnej skali po realne odstępy w komponentach i sekcjach.">
<StudioFrame title="Spacing Studio" description="Ruler, rhythm, and layout previews instead of raw numeric values—from global scale to real spacing in components and sections.">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/components/editors/SpacingStudio.svelte` at line 11, The
SpacingStudio description currently mixes Polish and English in the user-facing
text, which makes the UI inconsistent. Update the description on the StudioFrame
in SpacingStudio.svelte so it uses one locale consistently, matching the rest of
the English UI, and keep the wording aligned with the “Spacing Studio” title and
its layout/rhythm preview purpose.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
configurator/src/components/editors/LayoutStudio.svelte (1)

13-15: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use ordered-list semantics for workflow steps.

Since this is a numbered process, rendering steps as <ol><li> inside the nav will improve screen-reader semantics over plain <span> elements.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/components/editors/LayoutStudio.svelte` around lines 13 -
15, The workflow steps in LayoutStudio should use ordered-list semantics instead
of plain spans. Update the markup inside the nav for the workflow rendering so
the {`#each` workflow as step, index (step)} loop produces an ordered list with
list items, preserving the existing numbering and text. Keep the aria-label on
the nav and make sure the step rendering remains keyed by step while switching
to semantic list elements.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@configurator/src/components/editors/MotionStudio.svelte`:
- Around line 58-83: The animated previews in MotionStudio.svelte ignore users’
reduced-motion preference, so update the motion preview styles to respect
prefers-reduced-motion. In the MotionStudio component, specifically around the
motion-rails, easing-lab, preset-lab, reduced-lab, and keyframes like
move/enter/exit/shimmer, add a reduced-motion branch that disables or greatly
minimizes infinite animations and transitions when the media query matches,
while keeping the preview content readable and functional.

---

Nitpick comments:
In `@configurator/src/components/editors/LayoutStudio.svelte`:
- Around line 13-15: The workflow steps in LayoutStudio should use ordered-list
semantics instead of plain spans. Update the markup inside the nav for the
workflow rendering so the {`#each` workflow as step, index (step)} loop produces
an ordered list with list items, preserving the existing numbering and text.
Keep the aria-label on the nav and make sure the step rendering remains keyed by
step while switching to semantic list elements.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 91cce83d-544c-4617-9fa3-39200470eb1c

📥 Commits

Reviewing files that changed from the base of the PR and between 7253eb7 and 3773e26.

📒 Files selected for processing (7)
  • configurator/src/components/editors/EffectsStudio.svelte
  • configurator/src/components/editors/LayoutStudio.svelte
  • configurator/src/components/editors/MotionStudio.svelte
  • configurator/src/components/editors/ShadowStudio.svelte
  • configurator/src/components/editors/ShapeStudio.svelte
  • configurator/src/components/editors/StudioWorkflow.svelte
  • configurator/tests-components/studios.test.js
✅ Files skipped from review due to trivial changes (1)
  • configurator/src/components/editors/StudioWorkflow.svelte
🚧 Files skipped from review as they are similar to previous changes (3)
  • configurator/src/components/editors/ShadowStudio.svelte
  • configurator/src/components/editors/EffectsStudio.svelte
  • configurator/tests-components/studios.test.js

Comment on lines +58 to +83
.motion-rails span { width: 88px; text-align: center; padding: 8px 12px; border-radius: 999px; background: var(--cfg-accent-strong); color: white; animation: move var(--sf-duration-normal, 300ms) var(--sf-ease-in-out, ease-in-out) infinite alternate; }
.motion-rails .instant { animation-duration: var(--sf-duration-instant, 75ms); }
.motion-rails .fast { animation-duration: var(--sf-duration-fast, 150ms); }
.motion-rails .slow { animation-duration: var(--sf-duration-slow, 600ms); }
.motion-rails b { color: var(--cfg-text-muted); font-size: 11px; }
.easing-lab { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.easing-lab article { display: grid; gap: 8px; padding: 12px; }
.easing-lab b { text-transform: uppercase; font-size: 11px; letter-spacing: .06em; }
.easing-lab span { display: block; height: 58px; border-left: 1px solid var(--cfg-border-strong); border-bottom: 1px solid var(--cfg-border-strong); background: linear-gradient(135deg, transparent 48%, var(--cfg-accent-strong) 49% 52%, transparent 53%); border-radius: 8px; }
.easing-lab .in { background: radial-gradient(circle at 85% 85%, var(--cfg-accent-strong) 0 6px, transparent 7px), linear-gradient(145deg, transparent 58%, var(--cfg-accent-strong) 59% 62%, transparent 63%); }
.easing-lab .out { background: radial-gradient(circle at 20% 20%, var(--cfg-accent-strong) 0 6px, transparent 7px), linear-gradient(145deg, transparent 38%, var(--cfg-accent-strong) 39% 42%, transparent 43%); }
.easing-lab .spring { background: repeating-radial-gradient(ellipse at 50% 70%, transparent 0 10px, color-mix(in oklab, var(--cfg-accent-strong) 70%, transparent) 11px 13px); }
.preset-lab { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.preset-lab article { min-height: 96px; display: grid; place-items: center; text-transform: uppercase; font-weight: 900; letter-spacing: .06em; transition: transform var(--sf-duration-normal) var(--sf-ease-out), box-shadow var(--sf-duration-normal) var(--sf-ease-out), opacity var(--sf-duration-fast) var(--sf-ease-in); }
.enter { animation: enter var(--sf-duration-normal) var(--sf-ease-out) infinite alternate; }
.hover:hover { transform: translateY(-4px) scale(1.02); box-shadow: var(--sf-shadow-m); }
.exit { animation: exit var(--sf-duration-slow) var(--sf-ease-in) infinite alternate; }
.shimmer { background: linear-gradient(90deg, var(--cfg-bg-2), var(--cfg-accent-soft), var(--cfg-bg-2)); background-size: 220% 100%; animation: shimmer var(--sf-duration-slower, 900ms) var(--sf-ease-in-out) infinite; }
.reduced-lab { display: grid; gap: 8px; padding: 14px; }
.reduced-lab p { margin: 0; }
.reduced-lab div { display: flex; gap: 8px; }
.reduced-lab span { inline-size: 36px; block-size: 10px; border-radius: 999px; background: var(--cfg-accent-soft); opacity: var(--sf-state-pending-opacity, .65); }
@keyframes move { to { transform: translateX(min(360px, 42vw)); } }
@keyframes enter { from { transform: translateY(8px); opacity: .4; } to { transform: translateY(0); opacity: 1; } }
@keyframes exit { from { opacity: 1; } to { opacity: .45; transform: scale(.96); } }
@keyframes shimmer { to { background-position: -220% 0; } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Respect prefers-reduced-motion for animated previews.

This view runs several infinite animations even for users who explicitly request reduced motion, which is an accessibility blocker.

Proposed fix
   `@keyframes` shimmer { to { background-position: -220% 0; } }
+  `@media` (prefers-reduced-motion: reduce) {
+    .motion-rails span,
+    .enter,
+    .exit,
+    .shimmer {
+      animation: none !important;
+      transform: none !important;
+    }
+    .preset-lab article {
+      transition: none;
+    }
+  }
   `@media` (max-width: 860px) { .duration-lab, .easing-lab, .preset-lab { grid-template-columns: 1fr 1fr; } }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.motion-rails span { width: 88px; text-align: center; padding: 8px 12px; border-radius: 999px; background: var(--cfg-accent-strong); color: white; animation: move var(--sf-duration-normal, 300ms) var(--sf-ease-in-out, ease-in-out) infinite alternate; }
.motion-rails .instant { animation-duration: var(--sf-duration-instant, 75ms); }
.motion-rails .fast { animation-duration: var(--sf-duration-fast, 150ms); }
.motion-rails .slow { animation-duration: var(--sf-duration-slow, 600ms); }
.motion-rails b { color: var(--cfg-text-muted); font-size: 11px; }
.easing-lab { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.easing-lab article { display: grid; gap: 8px; padding: 12px; }
.easing-lab b { text-transform: uppercase; font-size: 11px; letter-spacing: .06em; }
.easing-lab span { display: block; height: 58px; border-left: 1px solid var(--cfg-border-strong); border-bottom: 1px solid var(--cfg-border-strong); background: linear-gradient(135deg, transparent 48%, var(--cfg-accent-strong) 49% 52%, transparent 53%); border-radius: 8px; }
.easing-lab .in { background: radial-gradient(circle at 85% 85%, var(--cfg-accent-strong) 0 6px, transparent 7px), linear-gradient(145deg, transparent 58%, var(--cfg-accent-strong) 59% 62%, transparent 63%); }
.easing-lab .out { background: radial-gradient(circle at 20% 20%, var(--cfg-accent-strong) 0 6px, transparent 7px), linear-gradient(145deg, transparent 38%, var(--cfg-accent-strong) 39% 42%, transparent 43%); }
.easing-lab .spring { background: repeating-radial-gradient(ellipse at 50% 70%, transparent 0 10px, color-mix(in oklab, var(--cfg-accent-strong) 70%, transparent) 11px 13px); }
.preset-lab { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.preset-lab article { min-height: 96px; display: grid; place-items: center; text-transform: uppercase; font-weight: 900; letter-spacing: .06em; transition: transform var(--sf-duration-normal) var(--sf-ease-out), box-shadow var(--sf-duration-normal) var(--sf-ease-out), opacity var(--sf-duration-fast) var(--sf-ease-in); }
.enter { animation: enter var(--sf-duration-normal) var(--sf-ease-out) infinite alternate; }
.hover:hover { transform: translateY(-4px) scale(1.02); box-shadow: var(--sf-shadow-m); }
.exit { animation: exit var(--sf-duration-slow) var(--sf-ease-in) infinite alternate; }
.shimmer { background: linear-gradient(90deg, var(--cfg-bg-2), var(--cfg-accent-soft), var(--cfg-bg-2)); background-size: 220% 100%; animation: shimmer var(--sf-duration-slower, 900ms) var(--sf-ease-in-out) infinite; }
.reduced-lab { display: grid; gap: 8px; padding: 14px; }
.reduced-lab p { margin: 0; }
.reduced-lab div { display: flex; gap: 8px; }
.reduced-lab span { inline-size: 36px; block-size: 10px; border-radius: 999px; background: var(--cfg-accent-soft); opacity: var(--sf-state-pending-opacity, .65); }
@keyframes move { to { transform: translateX(min(360px, 42vw)); } }
@keyframes enter { from { transform: translateY(8px); opacity: .4; } to { transform: translateY(0); opacity: 1; } }
@keyframes exit { from { opacity: 1; } to { opacity: .45; transform: scale(.96); } }
@keyframes shimmer { to { background-position: -220% 0; } }
.motion-rails span { width: 88px; text-align: center; padding: 8px 12px; border-radius: 999px; background: var(--cfg-accent-strong); color: white; animation: move var(--sf-duration-normal, 300ms) var(--sf-ease-in-out, ease-in-out) infinite alternate; }
.motion-rails .instant { animation-duration: var(--sf-duration-instant, 75ms); }
.motion-rails .fast { animation-duration: var(--sf-duration-fast, 150ms); }
.motion-rails .slow { animation-duration: var(--sf-duration-slow, 600ms); }
.motion-rails b { color: var(--cfg-text-muted); font-size: 11px; }
.easing-lab { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.easing-lab article { display: grid; gap: 8px; padding: 12px; }
.easing-lab b { text-transform: uppercase; font-size: 11px; letter-spacing: .06em; }
.easing-lab span { display: block; height: 58px; border-left: 1px solid var(--cfg-border-strong); border-bottom: 1px solid var(--cfg-border-strong); background: linear-gradient(135deg, transparent 48%, var(--cfg-accent-strong) 49% 52%, transparent 53%); border-radius: 8px; }
.easing-lab .in { background: radial-gradient(circle at 85% 85%, var(--cfg-accent-strong) 0 6px, transparent 7px), linear-gradient(145deg, transparent 58%, var(--cfg-accent-strong) 59% 62%, transparent 63%); }
.easing-lab .out { background: radial-gradient(circle at 20% 20%, var(--cfg-accent-strong) 0 6px, transparent 7px), linear-gradient(145deg, transparent 38%, var(--cfg-accent-strong) 39% 42%, transparent 43%); }
.easing-lab .spring { background: repeating-radial-gradient(ellipse at 50% 70%, transparent 0 10px, color-mix(in oklab, var(--cfg-accent-strong) 70%, transparent) 11px 13px); }
.preset-lab { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.preset-lab article { min-height: 96px; display: grid; place-items: center; text-transform: uppercase; font-weight: 900; letter-spacing: .06em; transition: transform var(--sf-duration-normal) var(--sf-ease-out), box-shadow var(--sf-duration-normal) var(--sf-ease-out), opacity var(--sf-duration-fast) var(--sf-ease-in); }
.enter { animation: enter var(--sf-duration-normal) var(--sf-ease-out) infinite alternate; }
.hover:hover { transform: translateY(-4px) scale(1.02); box-shadow: var(--sf-shadow-m); }
.exit { animation: exit var(--sf-duration-slow) var(--sf-ease-in) infinite alternate; }
.shimmer { background: linear-gradient(90deg, var(--cfg-bg-2), var(--cfg-accent-soft), var(--cfg-bg-2)); background-size: 220% 100%; animation: shimmer var(--sf-duration-slower, 900ms) var(--sf-ease-in-out) infinite; }
.reduced-lab { display: grid; gap: 8px; padding: 14px; }
.reduced-lab p { margin: 0; }
.reduced-lab div { display: flex; gap: 8px; }
.reduced-lab span { inline-size: 36px; block-size: 10px; border-radius: 999px; background: var(--cfg-accent-soft); opacity: var(--sf-state-pending-opacity, .65); }
`@keyframes` move { to { transform: translateX(min(360px, 42vw)); } }
`@keyframes` enter { from { transform: translateY(8px); opacity: .4; } to { transform: translateY(0); opacity: 1; } }
`@keyframes` exit { from { opacity: 1; } to { opacity: .45; transform: scale(.96); } }
`@keyframes` shimmer { to { background-position: -220% 0; } }
`@media` (prefers-reduced-motion: reduce) {
.motion-rails span,
.enter,
.exit,
.shimmer {
animation: none !important;
transform: none !important;
}
.preset-lab article {
transition: none;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/components/editors/MotionStudio.svelte` around lines 58 -
83, The animated previews in MotionStudio.svelte ignore users’ reduced-motion
preference, so update the motion preview styles to respect
prefers-reduced-motion. In the MotionStudio component, specifically around the
motion-rails, easing-lab, preset-lab, reduced-lab, and keyframes like
move/enter/exit/shimmer, add a reduced-motion branch that disables or greatly
minimizes infinite animations and transitions when the media query matches,
while keeping the preview content readable and functional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant